/**
 * Package:com.goldcitynet.robot;
 * $Id: Robot.java,v 1.2 2009/03/27 06:30:14 yangc Exp $
 * Copyright(c) 2001-2005 www.afteryuan.com
 */
package com.afteryuan.robot;

import com.afteryuan.server.command.down.CommandDown;
import com.afteryuan.server.command.down.core.MoveAvatarDown;
import com.afteryuan.server.command.up.CommandUp;
import com.afteryuan.server.command.up.core.JoinRoomUp;
import com.afteryuan.server.command.up.core.LoginUp;
import com.afteryuan.server.command.up.core.NullUp;
import com.afteryuan.server.command.up.core.RoadPathUp;
import com.afteryuan.util.ConfigHelper;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.ASObject;
import flex.messaging.io.amf.Amf3Input;
import flex.messaging.io.amf.Amf3Output;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Robot
 * <p/>
 * <p><a href="Robot.java.html"><i>View Source</i></a></p>
 *
 * @author <a href="mailto:afteryuan@afteryuan.com">$Author: yangc $</a>
 * @version $Revision: 1.2 $
 */
public class Robot extends Thread {
    private static Log log = LogFactory.getLog(Robot.class);
    private String scene;
    private String sender;
    private boolean readHead = true;
    private int packageLength;
    private byte[] temp = null;
    private Integer currentPoint;
    private Integer nextPoint;
    private Location[] locations;
    private Integer policy;
    private Location bornLocation;
    private static final int STEP_TIME = 50;
    private long forzentimeMax = 1000 * 20;
    private long forzentimeMin = 1000 * 5;
    private InetAddress host;
    private int port = 8099;
    private SocketChannel channel;
    private ByteBuffer bufferWrite;
    private boolean stop;
    private final ConcurrentLinkedQueue<CommandQueue> messageQueue = new ConcurrentLinkedQueue<CommandQueue>();
    private long lastSendTime;
    private Logout logout;
    /*for statistic*/
    private long totalTime;
    private long count;
    private long maxTime;
    private long minTime = 1000;
    private String sceneInstance;

    public Robot(String scene, String sceneInstance, String sender, String hostName, int port) {
        this.scene = scene;
        this.sceneInstance = sceneInstance;
        this.sender = sender;
        this.port = port;
        try {
            host = InetAddress.getByName(hostName);
            initScene();
            // 创建SocketChannel
            channel = SocketChannel.open(new InetSocketAddress(host, port));
            channel.configureBlocking(false);

            stop = false;
            bufferWrite = ByteBuffer.allocate(1024 * 10);

            /***********************登录过程*******************************/
            //1.登录服务器场景
            LoginUp loginUp = new LoginUp();
            loginUp.sender = sender;
            writeMessage(loginUp);
            //2.加入场景
            JoinRoomUp join = new JoinRoomUp();
            join.sender = sender;
            join.sceneId = scene;
            join.sceneInstanceId = sceneInstance;
            writeMessage(join);
            /*************************从出生点进入路点,默认进入第***********************/
            RoadPathUp roadPathUp = new RoadPathUp();
            roadPathUp.sender = sender;
            roadPathUp.sceneId = scene;
            roadPathUp.sceneInstanceId = sceneInstance;
            roadPathUp.startX = bornLocation.x;
            roadPathUp.startY = bornLocation.y;
            nextPoint();
            roadPathUp.targetX = locations[nextPoint].x;
            roadPathUp.targetY = locations[nextPoint].y;
            roadPathUp.speed=1;
            roadPathUp.delay=0;
            writeMessage(roadPathUp);


            log.debug("login successfully!");
            if (logout != null) logout.appendLog(sender + " login successfully!");

        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void start() {
        if (logout != null) logout.appendLog("Robot " + sender + " Start");
        super.start();
        /**********************开启写线程，处理消息队列*****************/
        new WriteTread().start();
    }

    public void run() {
        try {
            // 创建Selector
            Selector selector = Selector.open();
            // 向Selector注册我们需要的READ事件
            SelectionKey skey = channel.register(selector, SelectionKey.OP_READ);
            int n = 0;
            int read = 0;
            ByteBuffer bufferRead = ByteBuffer.allocate(1024 * 10);
            /**********************程序体进入主循环************************/
            // 轮询
            while (!stop) {
                // 获取Selector返回的时间值
                n = selector.select();
                // 当传回的值大于0事，读时间发生了
                if (n > 0) {
                    Set set = selector.selectedKeys();
                    Iterator it = set.iterator();
                    while (it.hasNext()) {
                        skey = (SelectionKey) it.next();
                        it.remove();
                        if (skey.isReadable()) {
                            SocketChannel sc = (SocketChannel) skey.channel();
                            while ((read = sc.read(bufferRead)) != -1) {
                                if (read == 0) break;
                                //开始读取对象
                                bufferRead.mark();
                                InputStream in = new ByteArrayInputStream(bufferRead.array());

                                while (in.available()>0) {
//InputStream in = new BufferInputStream(bufferRead);
                                    Object o = readObject(SerializationContext.getSerializationContext(), in);
                                    //默认读取出的对象必须为Command,否则认为读取失败
                                    if (o != null && o instanceof CommandDown) {
                                        process((CommandDown) o);
                                    } else {
                                        //如果读取对象失败,说明数据不完整,则将位置重置到标记处,返回false
                                        //CumulativeProtocolDecoder 会将剩余数据放入session中,跟下次来的数据汇合,在进行解码
                                        //bufferRead.reset();
                                    }
                                }
                                //-----------------

                            }
                        }
                    }
                }
            }

            channel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private void initScene() {
        SAXReader reader = new SAXReader();
        Document document = null;
        log.debug(ConfigHelper.findAsResource("roadmap/" + scene + ".xml"));
        log.debug(("roadmap/" + scene + ".xml"));

        try {
            document = reader.read(ConfigHelper.findAsResource("roadmap/" + scene + ".xml"));
//            document = reader.read("D:\\Project\\goldcity_robot\\src\\roadmap\\" + scene + ".xml");
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        String charX = document.selectSingleNode("//scene/born/x").getText();
        String charY = document.selectSingleNode("//scene/born/y").getText();
        bornLocation = new Location(Integer.parseInt(charX), Integer.parseInt(charY));
        nextPoint = 0;//表示出生点
        policy = Integer.parseInt(document.selectSingleNode("//scene/road/policy").getText());
        List<Element> pointNodes = document.selectNodes("//scene/road/roadpoints/point");
        locations = new Location[pointNodes.size()];
        for (int i = 0; i < pointNodes.size(); i++) {
            Element pointNode = pointNodes.get(i);
            String x = pointNode.selectSingleNode("x").getText();
            String y = pointNode.selectSingleNode("y").getText();
            locations[i] = new Location(Integer.parseInt(x), Integer.parseInt(y));
        }
        log.debug("Data Ready!");
    }


    /**
     * 发送消息。首先要防止空消息。一条消息只能有一个结束符号"\0"，如果出现两个结束符号并列的情况，则表示会发送一条空消息到服务器。<br/>
     * 服务器会报告NullPointerException。<br/>
     * 一条消息也必须有一个结束符号。如果message没有的话，就自动给它添上。<br/>
     *
     * @param command 要发送的命令
     * @param stamp   可选时间戳，在指定的时间之后发送，无此参数时马上发送
     */
    private void writeMessage(CommandUp command, Object... stamp) {
        if (stamp != null && stamp.length > 0) messageQueue.offer(new CommandQueue(command, (Long) stamp[0]));
        else messageQueue.offer(new CommandQueue(command));
    }


    /**
     * 根据返回的命令作出相应的逻辑处理
     *
     * @param command
     */
    private void process(CommandDown command) {
        if (command.handler.endsWith("moveAvatarHandler")) {
            moveNext((MoveAvatarDown) command);
        }
    }

    @Override
    public void interrupt() {
        super.interrupt();
        stop = true;
    }

    /**
     * 根据移动到下一个点
     *
     * @param command
     */
    private void moveNext(MoveAvatarDown command) {
        if (command.avatar.avatarId.equals(sender)) {
            long sendTime = command.createTime;
            long now = new Date().getTime();
            long waste = now - sendTime;
            log.debug(sender + " waste time ：" + now + "-" + sendTime + "=" + waste);
            /*send static data*/
            totalTime += waste;
            count++;
            if (waste > maxTime) maxTime = waste;
            if (waste < minTime) minTime = waste;
            log.info(sender + "," + command.handler + "," + now + "," + sendTime + "," + waste);
            if (count % 2 == 0) {
                MainModel.getInstance().update(count, totalTime, maxTime, minTime);
                count = 0;
                totalTime = 0;
            }

            if (logout != null) logout.appendLog(sender + " waste time ：" + waste);
            int pathTime = calculatePath(command);

            if (logout != null) logout.appendLog("Robot:" + sender + " current:" + currentPoint + " next:" + nextPoint);
            double r = (Math.random() * (forzentimeMax - forzentimeMin));
            RoadPathUp roadPathUp = new RoadPathUp();
            roadPathUp.sender = sender;
            roadPathUp.sceneId = scene;
            roadPathUp.sceneInstanceId = sceneInstance;
            roadPathUp.startX = locations[currentPoint].x;
            roadPathUp.startY = locations[currentPoint].y;
            roadPathUp.targetX = locations[nextPoint].x;
            roadPathUp.targetY = locations[nextPoint].y;
            roadPathUp.speed=1;
            roadPathUp.delay=0;
            writeMessage(roadPathUp, pathTime + forzentimeMin + (long) r + new Date().getTime());
            nextPoint();
        }
    }


    /**
     * 移动游标到下个位置
     */
    private void nextPoint() {
        currentPoint = nextPoint;
        if (locations.length == 0) nextPoint = 0;
        switch (policy) {
            case 0:
                nextPoint = new Random().nextInt(locations.length);
                break;
            case 1:
                nextPoint = (nextPoint >= new Random().nextInt(locations.length)) ? 0 : nextPoint + 1;
                break;
            default:
                nextPoint = 0;
        }
    }

    /**
     * 根据命令路径，计算行走所需时间
     *
     * @param command
     * @return
     */
    private Integer calculatePath(MoveAvatarDown command) {
        return command.avatar.listPane.length * STEP_TIME;

    }


    //调用AMF3的解码方法,读取命令对象
    private Object readObject(SerializationContext serializationContext, InputStream ins) {
        Amf3Input amfin = new Amf3Input(serializationContext);
        amfin.setInputStream(ins);
        Object o = null;
        try {
            o = amfin.readObject();
        } catch (EOFException e) {
            return null;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new ClassCastException(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (o instanceof ASObject) {
            return new NullUp();
        }
        return o;
    }


    public Logout getLogout() {
        return logout;
    }

    public void setLogout(Logout logout) {
        this.logout = logout;
    }

    public static void main(String[] args) throws Exception {
//        if (args == null || args.length < 4) {
//            System.out.println("paramater error!");
//            System.exit(1);
//        }
//        String scene = args[0];
//        String sceneInstance = args[1];
//        String sender = args[2];
//        String hostName = args[3];
//        int port = Integer.parseInt(args[3]);
//        new Robot(scene, sceneInstance, sender, hostName, port);
        Robot r = new Robot("4028810a219960980121996bfb700002", "4028810a219960980121996bfb700002", "001", "sockethost", 10088);
        r.start();
    }

    class WriteTread extends Thread {
        public void run() {
            while (!stop) {
                Iterator<CommandQueue> it = messageQueue.iterator();
                while (it.hasNext()) {
                    CommandQueue queue = it.next();
                    if (queue.stamp < new Date().getTime()) {
                        //if (queue.command instanceof MovingToCommand) queue.command.setSendTime(new Date().getTime());
                        bufferWrite.clear();
                        Amf3Output amfOut = new Amf3Output(SerializationContext.getSerializationContext());
                        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                        amfOut.setOutputStream(outStream);
                        try {
                            amfOut.writeObject(queue.command);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                        byte[] obyte = outStream.toByteArray();
                        bufferWrite.put(obyte);
                        bufferWrite.flip();
                        try {
                            while (bufferWrite.hasRemaining()) {
                                channel.write(bufferWrite);
                                lastSendTime = new Date().getTime();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        bufferWrite.clear();
                        it.remove();
                    }
                }
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }


    }
}

class CommandQueue {
    long stamp;
    CommandUp command;

    public CommandQueue(CommandUp message, long stamp) {
        this.stamp = stamp;
        this.command = message;
    }

    public CommandQueue(CommandUp message) {
        this.command = message;
    }


}

class Location {
    public Location(int x, int y) {
        this.x = x;
        this.y = y;
    }

    int x;
    int y;

}

class BufferInputStream extends InputStream {
    ByteBuffer byteBuffer;

    BufferInputStream(ByteBuffer byteBuffer) {
        this.byteBuffer = byteBuffer;
    }

    @Override
    public int available() {
        return byteBuffer.remaining();
    }

    @Override
    public synchronized void mark(int readlimit) {
        byteBuffer.mark();
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public int read() {
        if (byteBuffer.hasRemaining()) {
            return byteBuffer.get() & 0xff;
        }

        return -1;
    }

    @Override
    public int read(byte[] b, int off, int len) {
        int remaining = byteBuffer.remaining();
        if (remaining > 0) {
            int readBytes = Math.min(remaining, len);
            byteBuffer.get(b, off, readBytes);
            return readBytes;
        }

        return -1;
    }

    @Override
    public synchronized void reset() {
        byteBuffer.reset();
    }


}
